package service

import (
	"context"
	"database/sql"
	"encoding/json"
	"fmt"
	"github.com/pkg/errors"
	"github.com/smartwalle/alipay/v3"
	"gorm.io/gorm"
	"pay/api/pay"
	"pay/internal/constant"
	"pay/internal/generator"
	"pay/internal/model"
	"pay/pkg/ecode"
	"pay/pkg/orm"
	"pay/pkg/util"
	"strconv"
	"time"
)

// 查看可用支付方式
func (s *Service) GetAvailablePayTypes(ctx context.Context, req *pay.GetAvailablePayTypesRequest) (
	resp *pay.PayTypeList, err error) {
	var payType pay.PayType
	switch req.Source {
	case pay.SourceType_mall_mina:
		payType = pay.PayType_PAY_WECHAT
	case pay.SourceType_mall_alipay:
		payType = pay.PayType_PAY_ALIPAY
	case pay.SourceType_mall_byte:
		payType = pay.PayType_PAY_BYTEDANCE
	default:
		err = ecode.BadRequest("当前场景%s未对接", req.Source.String())
		return
	}
	if _, err = s.dao.GetStoreConfigByStoreId(
		ctx, req.SgId, req.PayStoreId, payType, req.BizType); err != nil {
		if errors.Cause(err) == gorm.ErrRecordNotFound {
			err = ecode.AppendMessage(constant.ErrForbiddenAction,
				"商家未配置%s收款账户", constant.PayNameMap[payType])
			return
		}
		return
	}
	resp = &pay.PayTypeList{PayTypes: []*pay.PayTypeView{{
		Id:   payType,
		Name: constant.PayNameMap[payType],
	}}}
	if req.BizType != pay.BizType_BIZ_RECHARGE {
		resp.PayTypes = append(resp.PayTypes, &pay.PayTypeView{
			Id:   pay.PayType_PAY_MEMBER,
			Name: constant.PayNameMap[pay.PayType_PAY_MEMBER],
		})
	}
	return
}

func formatPayResult(order *model.Order) (view *pay.PayOrderView) {
	payType := pay.PayType(order.PayType)
	view = &pay.PayOrderView{
		OrderNumber:      order.OrderNumber,
		Status:           pay.PayOrderStatus(order.Status),
		Source:           pay.SourceType(order.SourceType),
		PayType:          payType,
		OriginAmount:     order.Amount,
		MerchantDiscount: order.MerchantDiscount,
		PlatformDiscount: order.PlatformDiscount,
		UserPayed:        order.UserPayed,
		Receive:          order.Receive,
		RefundedAmount:   -int64(order.Refunded),
		PayTypeName:      constant.PayNameMap[payType],
		Remark:           order.Remark,
		BizNo:            order.BizNo,
		OrderKey:         order.OrderKey,
		BizType:          pay.BizType(order.BizType),
		CardInfo:         order.CardInfo,
		SgId:             order.SgId,
		StoreId:          order.PStoreId,
	}
	if order.PayTime.Valid {
		view.PayTime = order.PayTime.Time.Format(util.DefaultLayout)
	}
	return
}

// 创建支付单
func (s *Service) CreatePayOrder(ctx context.Context, req *pay.CreatePayOrderRequest) (
	resp *pay.DoPayOrderView, err error) {
	// 1. 支付订单锁，不允许同一个orderKey在同一个业务场景并发组合下单
	lockKey := fmt.Sprintf("pay/order/lock/%d/%s", req.BizType, req.OrderKey)
	if !s.dao.RedisClient.SetNX(lockKey, 1, time.Second*10).Val() {
		err = ecode.AppendMessage(constant.ErrForbiddenAction,
			"您的操作速度太快，请耐心等待下单结果，不要重复点击下单按钮")
		return
	} else {
		defer s.dao.RedisClient.Del(lockKey)
	}
	var orders []*model.Order
	// 2. 基于业务号过滤/置换重复支付单
	if orders, err = s.dao.FindValidPayOrdersByBizNo(ctx, req.BizType, req.BizNo); err != nil {
		return
	}
	for _, order := range orders {
		switch pay.PayOrderStatus(order.Status) {
		case pay.PayOrderStatus_PAY_ORDER_STATUS_PAYING:
			err = ecode.AppendMessage(constant.ErrForbiddenAction, "订单正在支付中，请耐心等待支付结果")
			return
		case pay.PayOrderStatus_PAY_ORDER_STATUS_SUCCESS:
			// 已支付成功，直接包装返回值返回
			resp = &pay.DoPayOrderView{
				OrderNumber:   order.OrderNumber,
				Status:        pay.PayOrderStatus_PAY_ORDER_STATUS_SUCCESS,
				Source:        pay.SourceType(order.SourceType),
				PayType:       pay.PayType(order.PayType),
				PayTypeName:   constant.PayNameMap[pay.PayType(order.PayType)],
				SuccessResult: formatPayResult(order),
				PrepayInfo:    constant.EmptyBytes,
			}
			return
		case pay.PayOrderStatus_REFUND_ORDER_STATUS_INIT:
			if order.TradeRemark == nil {
				if _, err = s.CancelPayOrder(ctx, &pay.CommonOrderRequest{OrderNumber: order.OrderNumber}); err != nil {
					return
				}
			} else {
				resp = &pay.DoPayOrderView{
					OrderNumber: order.OrderNumber,
					Status:      pay.PayOrderStatus_PAY_ORDER_STATUS_INIT,
					Source:      pay.SourceType(order.SourceType),
					PayType:     pay.PayType(order.PayType),
					PayTypeName: constant.PayNameMap[pay.PayType(order.PayType)],
					PrepayInfo:  order.TradeRemark,
				}
				return
			}
		}
	}
	var (
		order                            *model.Order
		payConfigJson, processRemarkJson json.RawMessage
		storeConfig                      *model.StoreConfig
		wechatConfig                     *model.WechatConfig
		byteDanceConfig                  *model.ByteDanceConfig
		token                            *model.AlipayToken
		remoteError                      error
		wechatRemark                     *model.WechatRemark
		alipayRemark                     *model.AlipayRemark
		byteDanceRemark                  *model.ByteDanceRemark
	)
	if req.Amount == 0 {
		req.PayType = pay.PayType_PAY_ZERO
	}
	switch req.PayType {
	case pay.PayType_PAY_MEMBER, pay.PayType_PAY_ZERO:
		payConfigJson = constant.EmptyBytes
	default:
		// 注意这里商城、充值、星团等业务均使用商城收款配置作为配置
		if storeConfig, err = s.dao.GetStoreConfigByStoreId(
			ctx, req.SgId, req.PayStoreId, req.PayType, pay.BizType_BIZ_MALL); err != nil {
			if errors.Cause(err) == gorm.ErrRecordNotFound {
				err = ecode.AppendMessage(constant.ErrForbiddenAction, "未配置收款账户，不能创建支付单")
				return
			}
			return
		}
		switch req.PayType {
		case pay.PayType_PAY_WECHAT:
			// 微信配置在电商售付通有存量，无需配置找回信息
			if wechatConfig, err = s.dao.GetWechatConfigById(ctx, storeConfig.AccountId, req.SgId); err != nil {
				return
			}
			wechatRemark = &model.WechatRemark{
				PayConfigRemark: model.PayConfigRemark{
					AppId:  req.AppId,
					OpenId: req.OpenId,
				},
				PStoreId:    wechatConfig.PStoreId,
				MerchantId:  wechatConfig.MerchantId,
				AccountType: wechatConfig.AccountType,
			}
		case pay.PayType_PAY_ALIPAY:
			var alipayConfig *model.AlipayConfig
			if alipayConfig, err = s.dao.GetAlipayConfigById(ctx, storeConfig.AccountId, req.SgId); err != nil {
				return
			}
			if token, err = s.dao.GetAlipayTokenByID(ctx, alipayConfig.TokenId); err != nil {
				return
			}
			alipayRemark = &model.AlipayRemark{
				PayConfigRemark: model.PayConfigRemark{
					AppId:  req.AppId,
					OpenId: req.OpenId,
				},
				ID: token.ID}
		case pay.PayType_PAY_BYTEDANCE:
			if byteDanceConfig, err = s.dao.GetByteDanceConfigById(ctx, storeConfig.AccountId, req.SgId); err != nil {
				return
			}
			byteDanceRemark = &model.ByteDanceRemark{
				PayConfigRemark: model.PayConfigRemark{
					AppId:  req.AppId,
					OpenId: req.OpenId,
				},
				ByteDanceAppId: byteDanceConfig.AppId}
		}
		if payConfigJson, err = json.Marshal(wechatRemark); err != nil {
			return
		}
	}
	now := time.Now()
	if err = s.dao.Transaction(ctx, func(ctx context.Context) (err error) {
		order = &model.Order{
			BizType:     uint8(req.BizType),
			BizNo:       req.BizNo,
			SourceType:  uint8(req.SourceType),
			PayType:     uint8(req.PayType),
			PayConfig:   orm.JSON(payConfigJson),
			CardInfo:    req.CardInfo,
			OrderKey:    req.OrderKey,
			Remark:      req.Remark,
			Amount:      req.Amount,
			PayTime:     sql.NullTime{},
			Status:      uint8(pay.PayOrderStatus_PAY_ORDER_STATUS_INIT),
			TradeRemark: constant.EmptyBytes,
			SgId:        req.SgId,
			PStoreId:    req.PayStoreId,
		}
		if order.OrderNumber, err = generator.GenerateOrderNumber(
			now, int(pay.OrderType_ORDER_TYPE_PAY), req.OrderKey); err != nil {
			return
		}
		if err = s.dao.Create(ctx, order); err != nil {
			return
		}
		if err = s.dao.Create(ctx, &model.Schedule{
			ScheduleType: uint8(pay.ScheduleType_SCHEDULE_TYPE_CLOSE),
			OrderId:      order.ID,
			ExecuteTime:  now.Add(time.Minute * 5),
		}); err != nil {
			return
		}
		resp = &pay.DoPayOrderView{
			OrderNumber: order.OrderNumber,
			Status:      pay.PayOrderStatus_PAY_ORDER_STATUS_INIT,
			Source:      req.SourceType,
			PayType:     req.PayType,
			PayTypeName: constant.PayNameMap[req.PayType],
			PrepayInfo:  constant.EmptyBytes,
		}
		processRemark := &model.OrderProcessRemark{}
		if len(req.Details) != 0 {
			var count uint32
			for _, detail := range req.Details {
				count += detail.Quantity
			}
			processRemark.Subject = fmt.Sprintf("%s等%d件商品",
				req.Details[0].ProdName[0:100], count)
			if req.PayType == pay.PayType_PAY_ALIPAY {
				processRemark.AlipayDetails = make([]*alipay.GoodsDetailItem, len(req.Details))
				for i, detail := range req.Details {
					processRemark.AlipayDetails[i] = &alipay.GoodsDetailItem{
						GoodsId:   fmt.Sprintf("%d-%d", detail.ProdId, detail.SkuId),
						GoodsName: fmt.Sprintf("%s/%s", detail.ProdName, detail.SkuName),
						Quantity:  strconv.Itoa(int(detail.Quantity)),
						Price:     fmt.Sprintf("%d.%d", detail.Price/100, detail.Price%100),
					}
				}
			}
		} else {
			processRemark.Subject = "会员订单"
		}
		if req.Submitted {
			// 直接提交模式
			switch req.PayType {
			case pay.PayType_PAY_MEMBER:
				order.Status = uint8(pay.PayOrderStatus_PAY_ORDER_STATUS_PAYING)
				if err = s.dao.DeleteSchedule(ctx,
					uint8(pay.ScheduleType_SCHEDULE_TYPE_CLOSE), order.ID); err != nil {
					return
				}
				if err = s.dao.Create(ctx, &model.Schedule{
					ScheduleType: uint8(pay.ScheduleType_SCHEDULE_TYPE_PAY_QUERY),
					OrderId:      order.ID,
					ExecuteTime:  now.Add(time.Second * 15),
				}); err != nil {
					return
				}
				if err = s.dao.Save(ctx, order); err != nil {
					return
				}
				return
			case pay.PayType_PAY_ZERO:
				if resp, err = s.doPayZero(ctx, order, now); err != nil {
					return
				}
				return
			default:
				switch req.PayType {
				case pay.PayType_PAY_WECHAT:
					if resp.PrepayInfo, err, remoteError = s.wechatCreate(
						ctx, order, wechatRemark, processRemark, now); err != nil {
						return
					}
				case pay.PayType_PAY_ALIPAY:
					if resp.PrepayInfo, err, remoteError = s.alipayCreate(
						order, alipayRemark, token, processRemark); err != nil {
						return
					}
				case pay.PayType_PAY_BYTEDANCE:
					if resp.PrepayInfo, err, remoteError = s.createPayByteDance(
						ctx, order, byteDanceRemark, processRemark); err != nil {
						return
					}
				}
				if remoteError == nil {
					order.TradeRemark = resp.PrepayInfo
				} else {
					order.Status = uint8(pay.PayOrderStatus_PAY_ORDER_STATUS_FAILED)
					if err = s.dao.DeleteSchedule(ctx, uint8(pay.ScheduleType_SCHEDULE_TYPE_CLOSE),
						order.ID); err != nil {
						return
					}
				}
				if err = s.dao.Save(ctx, order); err != nil {
					return
				}
			}
		} else {
			// 非立即提交，保存过程参数
			if processRemarkJson, err = json.Marshal(processRemark); err != nil {
				return
			}
			s.dao.RedisClient.Set(fmt.Sprintf(
				"pay/order/%s", order.OrderNumber), processRemarkJson, time.Minute*30)
		}
		return
	}); err != nil {
		return
	}
	if remoteError != nil {
		err = remoteError
		return
	}
	if req.Submitted && req.PayType == pay.PayType_PAY_MEMBER {
		// 会员支付必须放在事务外侧，防止调用超时后无法发起查询
		if resp, err = s.doPayMember(ctx, order); err != nil {
			return
		}
	}
	return
}

func (s *Service) formatProcessRemark(order *model.Order) (
	processRemark *model.OrderProcessRemark) {
	processRemark = &model.OrderProcessRemark{}
	processRemarkJson, _ := s.dao.RedisClient.Get(
		fmt.Sprintf("pay/order/%s", order.OrderNumber)).Result()
	if processRemarkJson != "" {
		_ = json.Unmarshal([]byte(processRemarkJson), processRemark)
	}
	if processRemark.Subject == "" {
		processRemark.Subject = "订单"
	}
	return
}

// 小程序预支付 / 直接发起0元单&会员支付
func (s *Service) DoPayment(ctx context.Context, req *pay.CommonOrderRequest) (
	resp *pay.DoPayOrderView, err error) {
	var remoteError error
	order := &model.Order{}
	now := time.Now()
	if err = s.dao.Transaction(ctx, func(ctx context.Context) (err error) {
		if err = s.dao.LockTradeByOrderNumber(ctx, order, req.OrderNumber); err != nil {
			if errors.Cause(err) == gorm.ErrRecordNotFound {
				err = constant.ErrObjectNotExist
				return
			}
			return
		}
		if pay.PayOrderStatus(order.Status) != pay.PayOrderStatus_PAY_ORDER_STATUS_INIT {
			err = ecode.AppendMessage(constant.ErrForbiddenAction, "订单状态不合法")
			return
		}
		payType := pay.PayType(order.PayType)
		switch payType {
		case pay.PayType_PAY_MEMBER:
			order.Status = uint8(pay.PayOrderStatus_PAY_ORDER_STATUS_PAYING)
			if err = s.dao.DeleteSchedule(ctx,
				uint8(pay.ScheduleType_SCHEDULE_TYPE_CLOSE), order.ID); err != nil {
				return
			}
			if err = s.dao.Create(ctx, &model.Schedule{
				ScheduleType: uint8(pay.ScheduleType_SCHEDULE_TYPE_PAY_QUERY),
				OrderId:      order.ID,
				ExecuteTime:  now.Add(time.Second * 15),
			}); err != nil {
				return
			}
		case pay.PayType_PAY_ZERO:
			if resp, err = s.doPayZero(ctx, order, now); err != nil {
				return
			}
			return
		default:
			resp = &pay.DoPayOrderView{
				OrderNumber: order.OrderNumber,
				Status:      pay.PayOrderStatus(order.Status),
				Source:      pay.SourceType(order.SourceType),
				PayType:     payType,
				PayTypeName: constant.PayNameMap[payType],
				PrepayInfo:  constant.EmptyBytes,
			}
			switch payType {
			case pay.PayType_PAY_WECHAT:
				config := &model.WechatRemark{}
				if err = json.Unmarshal(order.PayConfig, config); err != nil {
					return
				}
				if resp.PrepayInfo, err, remoteError = s.wechatCreate(
					ctx, order, config, s.formatProcessRemark(order), now); err != nil {
					return
				}
			case pay.PayType_PAY_ALIPAY:
				var token *model.AlipayToken
				config := &model.AlipayRemark{}
				if err = json.Unmarshal(order.PayConfig, config); err != nil {
					return
				}
				if token, err = s.dao.GetAlipayTokenByID(ctx, config.ID); err != nil {
					return
				}
				if resp.PrepayInfo, err, remoteError = s.alipayCreate(
					order, config, token, s.formatProcessRemark(order)); err != nil {
					return
				}
			case pay.PayType_PAY_BYTEDANCE:
				config := &model.ByteDanceRemark{}
				if err = json.Unmarshal(order.PayConfig, config); err != nil {
					return
				}
				if resp.PrepayInfo, err, remoteError = s.createPayByteDance(
					ctx, order, config, s.formatProcessRemark(order)); err != nil {
					return
				}
			}
			if remoteError == nil {
				order.TradeRemark = resp.PrepayInfo
			} else {
				order.Status = uint8(pay.PayOrderStatus_PAY_ORDER_STATUS_FAILED)
			}
			if err = s.dao.Save(ctx, order); err != nil {
				return
			}
		}
		return
	}); err != nil {
		return
	}
	if remoteError != nil {
		err = remoteError
		return
	}
	if pay.PayType(order.PayType) == pay.PayType_PAY_MEMBER {
		// 会员支付由于有前置定时任务落地，这里需要额外开事务
		if resp, err = s.doPayMember(ctx, order); err != nil {
			return
		}
	}
	return
}

func (s *Service) doQuery(ctx context.Context, orderId uint64) (order *model.Order, err error) {
	order = &model.Order{}
	if err = s.dao.Transaction(ctx, func(ctx context.Context) (err error) {
		if err = s.dao.LockTradeByOrderID(ctx, order, orderId); err != nil {
			if errors.Cause(err) == gorm.ErrRecordNotFound {
				err = constant.ErrObjectNotExist
				return
			}
			return
		}
		if pay.PayOrderStatus(order.Status) != pay.PayOrderStatus_PAY_ORDER_STATUS_PAYING {
			if err = s.dao.DeleteSchedule(ctx, uint8(pay.ScheduleType_SCHEDULE_TYPE_PAY_QUERY), order.ID); err != nil {
				return
			}
			return
		}
		switch pay.PayType(order.PayType) {
		case pay.PayType_PAY_WECHAT:
			if err = s.wechatQuery(ctx, order); err != nil {
				return
			}
		case pay.PayType_PAY_ALIPAY:
			if err = s.alipayQuery(ctx, order); err != nil {
				return
			}
		case pay.PayType_PAY_BYTEDANCE:
			if err = s.queryPayByteDance(ctx, order); err != nil {
				return
			}
		case pay.PayType_PAY_MEMBER:
			if err = s.memberQuery(ctx, order); err != nil {
				return
			}
		default:
			err = ecode.BadRequest("零元单无需触发支付成功任务")
			return
		}
		switch pay.PayOrderStatus(order.Status) {
		case pay.PayOrderStatus_PAY_ORDER_STATUS_PAYING:
			return
		default:
			if err = s.dao.Save(ctx, order); err != nil {
				return
			}
			if err = s.dao.DeleteSchedule(ctx, uint8(pay.ScheduleType_SCHEDULE_TYPE_PAY_QUERY), order.ID); err != nil {
				return
			}
			var payMessage json.RawMessage
			result := formatPayResult(order)
			if payMessage, err = json.Marshal(result); err != nil {
				return
			}
			if err = s.dao.PublishUniqueNSQMessage(ctx, constant.TopicPayFinish, payMessage); err != nil {
				return
			}
		}
		return
	}); err != nil {
		return
	}
	return
}

// 前端主动触发支付成功（开启查询定时任务）
func (s *Service) TriggerPaySuccess(ctx context.Context, req *pay.CommonOrderRequest) (
	resp *pay.PayOrderView, err error) {
	order := &model.Order{}
	if err = s.dao.Transaction(ctx, func(ctx context.Context) (err error) {
		if err = s.dao.LockTradeByOrderNumber(ctx, order, req.OrderNumber); err != nil {
			if errors.Cause(err) == gorm.ErrRecordNotFound {
				err = constant.ErrObjectNotExist
				return
			}
			return
		}
		if order.TradeRemark == nil {
			err = ecode.BadRequest("请先提交二阶段支付请求，再触发支付成功任务")
			return
		}

		if err = s.dao.DeleteSchedule(ctx, uint8(pay.ScheduleType_SCHEDULE_TYPE_CLOSE), order.ID); err != nil {
			return
		}
		if pay.PayOrderStatus(order.Status) != pay.PayOrderStatus_PAY_ORDER_STATUS_INIT {
			return
		}
		order.Status = uint8(pay.PayOrderStatus_PAY_ORDER_STATUS_PAYING)
		if err = s.dao.Create(ctx, &model.Schedule{
			ScheduleType: uint8(pay.ScheduleType_SCHEDULE_TYPE_PAY_QUERY),
			OrderId:      order.ID,
			ExecuteTime:  time.Now().Add(time.Second * 10),
		}); err != nil {
			return
		}
		return
	}); err != nil {
		return
	}
	if order, err = s.doQuery(ctx, order.ID); err != nil {
		return
	}
	resp = formatPayResult(order)
	return
}

// 前端主动触发支付成功（开启查询定时任务）
func (s *Service) GetPayOrderView(ctx context.Context, req *pay.CommonOrderRequest) (
	resp *pay.PayOrderView, err error) {
	var order *model.Order
	if order, err = s.dao.GetOrderByOrderNumber(ctx, req.OrderNumber); err != nil {
		return
	}
	resp = formatPayResult(order)
	return
}

// 取消未付款支付单
func (s *Service) CancelPayOrder(ctx context.Context, req *pay.CommonOrderRequest) (
	resp *pay.DefaultResp, err error) {
	order := &model.Order{}
	if err = s.dao.Transaction(ctx, func(ctx context.Context) (err error) {
		if err = s.dao.LockTradeByOrderNumber(ctx, order, req.OrderNumber); err != nil {
			return
		}
		switch pay.PayOrderStatus(order.Status) {
		case pay.PayOrderStatus_PAY_ORDER_STATUS_SUCCESS:
			err = ecode.AppendMessage(constant.ErrForbiddenAction, "订单支付中或已成功，不能取消支付")
			return
		case pay.PayOrderStatus_PAY_ORDER_STATUS_FAILED, pay.PayOrderStatus_PAY_ORDER_STATUS_CLOSED:
			resp = &pay.DefaultResp{Status: true}
			return
		case pay.PayOrderStatus_PAY_ORDER_STATUS_INIT, pay.PayOrderStatus_PAY_ORDER_STATUS_PAYING:
			if order.TradeRemark != nil {
				switch pay.PayType(order.PayType) {
				case pay.PayType_PAY_WECHAT:
					if err = s.downstream.TradeClose(ctx, order.SgId, order.OrderNumber); err != nil {
						return
					}
				case pay.PayType_PAY_ALIPAY:
					if err = s.alipayClose(ctx, order); err != nil {
						return
					}
				case pay.PayType_PAY_BYTEDANCE:
					diff := order.CreatedAt.Add(time.Minute * 5).Sub(time.Now()).Seconds()
					if diff > 0 {
						err = ecode.AppendMessage(constant.ErrForbiddenAction,
							"抖音订单暂时不支持关闭，还需等待%f秒方可取消", diff)
						return
					}
				default:
					err = ecode.AppendMessage(constant.ErrForbiddenAction, "订单不支持取消支付")
					return
				}
			}
			order.Status = uint8(pay.PayOrderStatus_PAY_ORDER_STATUS_CLOSED)
			if err = s.dao.Save(ctx, order); err != nil {
				return
			}
		default:
			panic(fmt.Sprintf("订单状态分支错误"))
		}
		return
	}); err != nil {
		return
	}
	resp = &pay.DefaultResp{Status: true}
	return
}

func formatRefundOrderView(order *model.Order, refund *model.Refund) *pay.RefundOrderView {
	view := &pay.RefundOrderView{
		OrderNumber:      refund.OrderNumber,
		Status:           pay.PayOrderStatus(refund.Status),
		Source:           pay.SourceType(order.SourceType),
		PayType:          pay.PayType(order.PayType),
		OriginAmount:     refund.Amount,
		MerchantDiscount: refund.MerchantDiscount,
		PlatformDiscount: refund.PlatformDiscount,
		UserPayed:        refund.UserPayed,
		Receive:          refund.Receive,
		PayTypeName:      constant.PayNameMap[pay.PayType(order.PayType)],
		Remark:           order.Remark,
		Reason:           refund.RefundReason,
		BizNo:            refund.BizNo,
		PayOrderNumber:   order.OrderNumber,
		OrderKey:         order.OrderKey,
		BizType:          pay.BizType(order.BizType),
		CardInfo:         order.CardInfo,
		SgId:             order.SgId,
		StoreId:          order.PStoreId,
		FailedReason:     refund.FailedReason,
	}
	if refund.PayTime.Valid {
		view.PayTime = refund.PayTime.Time.Format(util.DefaultLayout)
	}
	return view
}

// 创建退款单
func (s *Service) CreateRefundOrder(ctx context.Context, req *pay.CreateRefundOrderRequest) (
	resp *pay.RefundOrderView, err error) {
	var refund *model.Refund
	order := &model.Order{}
	if err = s.dao.Transaction(ctx, func(ctx context.Context) (err error) {
		var (
			orderNumber  string
			targetStatus pay.PayOrderStatus
		)
		if err = s.dao.LockTradeByOrderNumber(ctx, order, req.OrderNumber); err != nil {
			return
		}
		switch pay.PayType(order.PayType) {
		case pay.PayType_PAY_ZERO:
			// 需要检查是否存在退款单，当且仅当无退款单时，允许发起一次0元单退款
			if req.RefundAmount != 0 {
				err = ecode.AppendMessage(constant.ErrForbiddenAction, "退款金额必须为0")
				return
			}
			var refunds []*model.Refund
			if refunds, err = s.dao.FindRefundsByPayOrderId(ctx, order.ID); err != nil {
				return
			} else if len(refunds) > 0 {
				err = ecode.AppendMessage(constant.ErrForbiddenAction, "零元单只允许发起一次退款")
				return
			}
			err = nil
			targetStatus = pay.PayOrderStatus_REFUND_ORDER_STATUS_SUCCESS
		default:
			if req.RefundAmount > order.Amount-order.Refunded {
				err = ecode.AppendMessage(constant.ErrForbiddenAction, "支付单可余额不足")
				return
			} else if req.RefundAmount == 0 {
				err = ecode.AppendMessage(constant.ErrForbiddenAction, "退款金额不能为0")
				return
			}
			targetStatus = pay.PayOrderStatus_REFUND_ORDER_STATUS_INIT
		}
		now := time.Now()
		order.Refunded += req.RefundAmount
		if orderNumber, err = generator.GenerateOrderNumber(now, int(pay.OrderType_ORDER_TYPE_REFUND),
			order.OrderKey); err != nil {
			return
		}
		refund = &model.Refund{
			OrderNumber:      orderNumber,
			ParentId:         order.ID,
			BizType:          order.BizType,
			BizNo:            req.BizNo,
			OrderKey:         order.OrderKey,
			Amount:           req.RefundAmount,
			MerchantDiscount: 0,
			PlatformDiscount: 0,
			UserPayed:        req.RefundAmount,
			Receive:          req.RefundAmount,
			PayTime:          sql.NullTime{},
			Status:           uint8(targetStatus),
			RefundReason:     req.Reason,
			OutTradeNo:       orderNumber,
			TradeNo:          "",
			TradeRemark:      nil,
			SgId:             order.SgId,
			PStoreId:         order.PStoreId,
			Operator:         req.EmpId,
		}
		if err = s.dao.Create(ctx, refund); err != nil {
			return
		}
		if err = s.dao.Save(ctx, order); err != nil {
			return
		}
		if targetStatus == pay.PayOrderStatus_REFUND_ORDER_STATUS_SUCCESS {
			var refundMessage json.RawMessage
			resp = formatRefundOrderView(order, refund)
			if refundMessage, err = json.Marshal(resp); err != nil {
				err = ecode.WithStack(err)
				return
			}
			if err = s.dao.PublishUniqueNSQMessage(
				ctx, constant.TopicRefundFinish, refundMessage); err != nil {
				return
			}
		}
		return
	}); err != nil {
		return
	}
	if pay.PayOrderStatus(refund.Status) != pay.PayOrderStatus_REFUND_ORDER_STATUS_INIT {
		return
	}
	if err = s.dao.Transaction(ctx, func(ctx context.Context) (err error) {
		if err = s.dao.LockTradeByOrderNumber(ctx, refund, refund.OrderNumber); err != nil {
			return
		}
		if pay.PayOrderStatus(refund.Status) != pay.PayOrderStatus_REFUND_ORDER_STATUS_INIT {
			resp = formatRefundOrderView(order, refund)
			return
		}
		switch pay.PayType(order.PayType) {
		case pay.PayType_PAY_MEMBER:
			s.memberRefund(ctx, order, refund)
		case pay.PayType_PAY_WECHAT:
			s.wechatRefund(ctx, order, refund)
		case pay.PayType_PAY_ALIPAY:
			s.alipayRefund(ctx, order, refund)
		case pay.PayType_PAY_BYTEDANCE:
			s.refundPayByteDance(ctx, order, refund)
		default:
			panic(fmt.Sprintf("支付方式分支错误，没有可用的退款方式: %d", order.PayType))
		}
		if err = s.dao.Save(ctx, refund); err != nil {
			return
		}
		resp = formatRefundOrderView(order, refund)
		switch pay.PayOrderStatus(refund.Status) {
		case pay.PayOrderStatus_REFUND_ORDER_STATUS_SUCCESS, pay.PayOrderStatus_REFUND_ORDER_STATUS_MARKED_SUCCESS,
			pay.PayOrderStatus_REFUND_ORDER_STATUS_FAILED:
			var refundMessage json.RawMessage
			if refundMessage, err = json.Marshal(resp); err != nil {
				err = ecode.WithStack(err)
				return
			}
			if err = s.dao.PublishUniqueNSQMessage(
				ctx, constant.TopicRefundFinish, refundMessage); err != nil {
				return
			}
		}
		return
	}); err != nil {
		return
	}
	return
}

// 退款失败重试/标记
func (s *Service) RefundRetry(ctx context.Context, req *pay.RefundRetryRequest) (
	resp *pay.RefundOrderView, err error) {
	if err = s.dao.Transaction(ctx, func(ctx context.Context) (err error) {
		var order *model.Order
		refund := &model.Refund{}
		if err = s.dao.LockTradeByOrderNumber(ctx, refund, req.OrderNumber); err != nil {
			if errors.Cause(err) == gorm.ErrRecordNotFound {
				err = constant.ErrObjectNotExist
				return
			}
			return
		}
		if order, err = s.dao.GetOrderByOrderId(ctx, refund.ParentId); err != nil {
			return
		}
		switch pay.PayOrderStatus(refund.Status) {
		case pay.PayOrderStatus_REFUND_ORDER_STATUS_INIT, pay.PayOrderStatus_REFUND_ORDER_STATUS_FAILED:
			if req.IsMarked {
				refund.Status = uint8(pay.PayOrderStatus_REFUND_ORDER_STATUS_MARKED_SUCCESS)
				refund.PayTime = sql.NullTime{Valid: true, Time: time.Now()}
				return
			} else {
				switch pay.PayType(order.PayType) {
				case pay.PayType_PAY_WECHAT:
					s.wechatRefund(ctx, order, refund)
				case pay.PayType_PAY_ALIPAY:
					s.alipayRefund(ctx, order, refund)
				case pay.PayType_PAY_BYTEDANCE:
					s.refundPayByteDance(ctx, order, refund)
				default:
					panic(fmt.Sprintf("支付方式分支错误，没有可重试的退款方式: %d", order.PayType))
				}
			}
		default:
			err = ecode.AppendMessage(constant.ErrForbiddenAction, "退款单状态不允许发起重试")
			return
		}
		if err = s.dao.Save(ctx, refund); err != nil {
			return
		}
		resp = formatRefundOrderView(order, refund)
		switch pay.PayOrderStatus(refund.Status) {
		case pay.PayOrderStatus_REFUND_ORDER_STATUS_SUCCESS, pay.PayOrderStatus_REFUND_ORDER_STATUS_MARKED_SUCCESS,
			pay.PayOrderStatus_REFUND_ORDER_STATUS_FAILED:
			var refundMessage json.RawMessage
			if refundMessage, err = json.Marshal(resp); err != nil {
				err = ecode.WithStack(err)
				return
			}
			if err = s.dao.PublishUniqueNSQMessage(
				ctx, constant.TopicRefundFinish, refundMessage); err != nil {
				return
			}
		}
		return
	}); err != nil {
		return
	}
	return
}

// 查看退款单详情（申请中退款单会更新退款状态）
func (s *Service) GetRefundOrder(ctx context.Context, req *pay.CommonQueryRequest) (
	resp *pay.RefundOrderView, err error) {
	var (
		refund *model.Refund
		order  *model.Order
	)
	if refund, err = s.dao.GetRefundByOrderNumber(ctx, req.OrderNumber); err != nil {
		return
	}
	if order, err = s.dao.GetOrderByOrderId(ctx, refund.ParentId); err != nil {
		return
	}
	resp = formatRefundOrderView(order, refund)
	return
}
